package com.rich.group.service.impl;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.rich.common.constant.GroupBuyEnum;
import com.rich.common.constant.MyGlobalConstant;
import com.rich.common.result.CommonResponse;
import com.rich.group.mapper.GroupBuyMapper;
import com.rich.group.mapper.GroupJoinMapper;
import com.rich.group.pojo.GroupBuy;
import com.rich.group.pojo.GroupJoin;
import com.rich.group.service.GroupJoinService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @author minicoco
 * @version 1.0
 * @date 2021/8/16 11:52
 */
@Slf4j
@Service
public class GroupJoinServiceImpl implements GroupJoinService {

    @Autowired
    private GroupJoinMapper groupJoinMapper;
    @Autowired
    private GroupBuyMapper groupBuyMapper;


    /**
     * redis 序列化配置 保证显示不乱吗
     */
    private RedisTemplate redisTemplate;
    @Autowired(required = false)
    public void setRedisTemplate(RedisTemplate redisTemplate) {
        //Json 序列化配置
        Jackson2JsonRedisSerializer objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        objectJackson2JsonRedisSerializer.setObjectMapper(om);

        RedisSerializer stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setValueSerializer(objectJackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.setHashValueSerializer(objectJackson2JsonRedisSerializer);
        this.redisTemplate = redisTemplate;
    }

    /**
     * 注入redisson
     */
    @Autowired(required = false)
    private Redisson redisson;
    /**
     * 添加rabbitmq模板
     */
    @Autowired
    private RabbitTemplate rabbitTemplate;



    /**
     * 我要报名
     * @param uid
     * @param groupId
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public CommonResponse signup(Long uid, Long groupId) {
        long startTime = System.currentTimeMillis();
        log.debug("{}开始报名参团...........",startTime);

        //随即生成用户id 方便压测使用
        Random random = new Random();
        int i = random.nextInt(100);
        uid=Integer.toUnsignedLong(i);
        log.info("本次参与团购的用户id为:{}",uid);

        //0.获取团购信息
        GroupBuy groupBuy = (GroupBuy) redisTemplate
                .opsForHash()
                .get(
                        MyGlobalConstant.GroupBuyConstant.GROUP_BUY,
                        MyGlobalConstant.GroupBuyConstant.GROUP_BUY_ITEM+groupId
                );

        if (groupBuy==null){
            return new CommonResponse(GroupBuyEnum.UN_CONFORMITY.getCode(),GroupBuyEnum.UN_CONFORMITY.getMessage());
        }
        //1.判断活动时间
        if (groupBuy.getStartTime().getTime()>System.currentTimeMillis()){
           //活动未开始
            return new CommonResponse(GroupBuyEnum.UN_START.getCode(),GroupBuyEnum.UN_START.getMessage());
        }else if (groupBuy.getEndTime().getTime()<System.currentTimeMillis()){
            //活动一结束
            return new CommonResponse(GroupBuyEnum.IS_OVER.getCode(), GroupBuyEnum.UN_START.getMessage());
        }else {
            //判断当前活动是否已经满员
            if (MyGlobalConstant.GroupBuyConstant.IS_END.equals(groupBuy.getStatus())){
                return new CommonResponse(GroupBuyEnum.IS_ENOUGH.getCode(), GroupBuyEnum.IS_ENOUGH.getMessage());
            }
            //2.判断该用户是否参与过该拼团活动
            //2.1从redis中取出用户的购买次数
            Map userGroupNum = redisTemplate.opsForHash().entries(MyGlobalConstant.GroupBuyConstant.USER_GROUP_NUM + groupId);
            //当前用户第一次访问
            if (userGroupNum.get(MyGlobalConstant.GroupBuyConstant.PRE_USER+uid)==null){
                //3.加入拼团
                /**
                 * 添加zk分布式锁
                 */
//                InterProcessMutex mutex = new InterProcessMutex(curatorFramework,lockPath+groupId);
                //设置分布式锁
                RLock lock = redisson.getLock(MyGlobalConstant.GroupBuyConstant.LOCK_GROUP_BUY+groupId);
                try {
                    lock.lock();
                    //给redis中添加数据
                    userGroupNum.put(MyGlobalConstant.GroupBuyConstant.PRE_USER+uid,1);
                    redisTemplate.opsForHash().putAll(MyGlobalConstant.GroupBuyConstant.USER_GROUP_NUM + groupId,userGroupNum);
                    //判断当前活动是否已经满员
                    if ( MyGlobalConstant.GroupBuyConstant.IS_END.equals(groupBuy.getStatus())){
                        return new CommonResponse(GroupBuyEnum.IS_ENOUGH.getCode(), GroupBuyEnum.IS_ENOUGH.getMessage());
                    }
                    //3.2修改redis参团人数
                    groupBuy.setJoinNum(groupBuy.getJoinNum()+1);
                    //3.4 判断是否满员
                    if (groupBuy.getJoinNum()>=groupBuy.getLimitNum()){
                        //满员 修改团购状态
                        groupBuy.setStatus(MyGlobalConstant.GroupBuyConstant.IS_END);
                        rabbitTemplate.convertAndSend(MyGlobalConstant.GroupBuyConstant.UPDATE_GROUP_STATUS,groupId);
                    }
                    redisTemplate.opsForHash()
                            .put(
                                    MyGlobalConstant.GroupBuyConstant.GROUP_BUY,
                                    MyGlobalConstant.GroupBuyConstant.GROUP_BUY_ITEM+groupId,
                                    groupBuy
                            );
                    //3.3添加用户参团记录
                    GroupJoin groupJoin = new GroupJoin();
                    groupJoin.setGroupId(groupId);
                    groupJoin.setJoinTime(new Date());
                    groupJoin.setUserId(uid);
                    rabbitTemplate.convertAndSend(MyGlobalConstant.GroupBuyConstant.ADD_GROUP_RECORD,groupJoin);

                    log.info("目前参与人数:{}人",groupBuy.getJoinNum());
                    log.info("限制参与人数:{}人",groupBuy.getLimitNum());
                    log.info("结束请求,共计耗时:{}ms",((System.currentTimeMillis()-startTime)));

                    return new CommonResponse(GroupBuyEnum.SUCCESS_JOIN.getCode() ,GroupBuyEnum.SUCCESS_JOIN.getMessage());
                } catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    //释放锁
                    lock.unlock();
                }
            }else {
                return new CommonResponse(GroupBuyEnum.ALREADY_JOIN.getCode(), GroupBuyEnum.ALREADY_JOIN.getMessage());
            }
        }
        return null;
    }

    @Override
    public void deleteById(Long id) {
        GroupJoin oneById = groupJoinMapper.findOneById(id);
        groupJoinMapper.deleteById(id);
        //删除redis中的用户参与次数
        Map userGroupNum = redisTemplate.opsForHash().entries("user_group_num:" + oneById.getGroupId());
        userGroupNum.remove("userId:"+oneById.getUserId());
        redisTemplate.delete("user_group_num:" + oneById.getGroupId());
        redisTemplate.opsForHash().putAll("user_group_num:" + oneById.getGroupId(),userGroupNum);
        groupBuyMapper.updateJoinNum(oneById.getGroupId(), -1);
    }

    @Override
    public GroupJoin findOneById(Long id) {
        GroupJoin groupJoin = groupJoinMapper.findOneById(id);
        return groupJoin;
    }
}
